//
// Created by Mengyu Li on 2021/2/18.
//

import AppCenter
import AppCenterAnalytics
import AppCenterCrashes
import Darwin.Mach
import Foundation
import LogMan
import Standard

public enum PerformanceMonitor {}

extension PerformanceMonitor: TypeName {}

@available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
private extension PerformanceMonitor {
    static let logger = Loggers[typeName]
    static let queue = DispatchQueue(label: typeName, qos: .background)
    static let timer = DispatchTimer(interval: .seconds(10), repeats: true, queue: queue, handler: timerHandle)
}

@available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
public extension PerformanceMonitor {
    static func start(appSecret: String) {
        precondition(!appSecret.isEmpty, "missing app secret")
        AppCenter.start(withAppSecret: appSecret, services: [
            Analytics.self,
            Crashes.self,
        ])

        #if targetEnvironment(simulator)
            Crashes.enabled = false
        #endif
    }

    static func startMonitoring() {
        queue.async { timer.start() }
    }

    static func stopMonitoring() {
        queue.async { timer.suspend() }
    }
}

@available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
private extension PerformanceMonitor {
    static func timerHandle(timer _: DispatchTimer) {
        let memory = appMemory()
        let cpuUsage = appCPU()
        let out = String(format: "memory usage : %.2fM - cpu usage : %.2f%%", Float(memory) / 1024.0 / 1024.0, cpuUsage * 100)
        logger.info("\(out)")
    }
}

@available(OSX 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)
private extension PerformanceMonitor {
    static func appMemory() -> mach_vm_size_t {
        var vmInfo = task_vm_info_data_t()
        var count = mach_msg_type_number_t(MemoryLayout<task_vm_info_data_t>.stride / MemoryLayout<natural_t>.stride)
        let kernelReturn = withUnsafeMutablePointer(to: &vmInfo) {
            $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
                task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count)
            }
        }
        guard kernelReturn == KERN_SUCCESS
        else {
            logger.error("ERROR : \(kernelReturn)")
            return 0
        }
        return vmInfo.phys_footprint
    }

    static func appCPU() -> Float {
        var threadListOptional: thread_array_t?
        var count: mach_msg_type_number_t = 0
        let kernelReturn = task_threads(mach_task_self_, &threadListOptional, &count)
        guard kernelReturn == KERN_SUCCESS, let threadList = threadListOptional
        else {
            logger.error("ERROR : \(kernelReturn)")
            return -1
        }

        var cpuUsage: Float = 0
        var threadInfoCount = mach_msg_type_number_t(THREAD_INFO_MAX)
        for index in 0 ..< count {
            var threadBasicInfo = thread_basic_info()
            let ret = withUnsafeMutablePointer(to: &threadBasicInfo) {
                $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
                    thread_info(threadList[Int(index)], thread_flavor_t(THREAD_BASIC_INFO), $0, &threadInfoCount)
                }
            }
            guard ret == KERN_SUCCESS else {
                logger.error("ERROR : \(ret)")
                return -1
            }

            if threadBasicInfo.flags & TH_FLAGS_IDLE != 1 {
                cpuUsage += Float(threadBasicInfo.cpu_usage)
            }
        }
        cpuUsage /= Float(TH_USAGE_SCALE)
        let address = vm_address_t(UInt(bitPattern: threadList))
        vm_deallocate(mach_task_self_, address, vm_size_t(Int(count) * MemoryLayout<thread_t>.stride))
        return cpuUsage
    }
}
